﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using MyFramework.Command.Interfaces;

namespace MyFramework.Command.DelegateWatchCommand
{
    /// <summary>
    /// property watch chain
    /// </summary>
    public class PropertyWatchChain
    {
        #region constructor

        /// <summary>
        /// Create a one element chain. 
        /// </summary>
        /// <param name="tail"></param>
        public PropertyWatchChain(IPropertyWatchTail tail)
        {
            if (tail == null)
            {
                throw new ArgumentNullException("tail");
            }
            this.Head = tail;
            tail.Parent = this;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Occurs when [watched property changed].
        /// </summary>
        public event EventHandler<EventArgs> WatchedPropertyChanged;

        /// <summary>
        /// Gets the head.
        /// </summary>
        public IPropertyWatch Head { get; private set; }

        #endregion

        #region Methods

        /// <summary>
        /// Froms the lambda.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr">The expr.</param>
        /// <returns></returns>
        public static PropertyWatchChain FromLambda<T>(Expression<Func<T, object>> expr)
        where T : INotifyPropertyChanged
        {
            var ret = Visitor.Visit(expr);
            return ret;
        }

        /// <summary>
        /// Prepend an element. 
        /// </summary>
        /// <param name="head"></param>
        /// <returns></returns>
        public PropertyWatchChain Cons(PropertyWatch head)
        {
            if (head == null)
            {
                throw new ArgumentNullException("head");
            }
            head.Next = this.Head;
            this.Head = head;
            return this;
        }

        /// <summary>
        /// Called when [watched property changed].
        /// </summary>
        internal void OnWatchedPropertyChanged()
        {
            var tmp = this.WatchedPropertyChanged;
            if (tmp != null)
                tmp(this, EventArgs.Empty);
        }


        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns></returns>
        internal PropertyWatchChain Clone()
        {
            return this.Clone(this.Head, null);
        }

        /// <summary>
        /// Clones the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="accumulate">The accumulate.</param>
        /// <returns></returns>
        private PropertyWatchChain Clone(IPropertyWatch item, PropertyWatchChain accumulate)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            var tail = item as IPropertyWatchTail;

            if (tail != null)
            {
                Debug.Assert(accumulate == null, "Accumulated result not empty when tail is reached");
                return new PropertyWatchChain(tail.Clone());
            }
            else
            {
                PropertyWatch pw = item as PropertyWatch;
                if (pw != null)
                {
                    var ret = this.Clone(pw.Next, accumulate);
                    return ret.Cons(pw.Clone());
                }
                return null;
            }
        }

        #endregion
    }
}
